package cn.xerllent.security.service;

import cn.xerllent.security.config.SecurityProperties;
import cn.xerllent.security.security.vo.JwtUser;
import cn.xerllent.security.security.vo.OnlineUser;
import cn.hutool.core.bean.BeanUtil;
import cn.xerllent.common.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 */
@Service
@Slf4j
public class OnlineUserService {

    private final SecurityProperties properties;
    private RedisUtils redisUtils;

    public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) {
        this.properties = properties;
        this.redisUtils = redisUtils;
    }

    /**
     * 保存在线用户信息
     * @param jwtUser /
     * @param token /
     * @param request /
     */
    public void save(JwtUser jwtUser, String token, HttpServletRequest request) {
        //String job = jwtUser.getDept() + "/" + jwtUser.getJob();
        String ip = StringUtils.getIp(request);
        String browser = StringUtils.getBrowser(request);
        String address = StringUtils.getCityInfo(ip);
        OnlineUser onlineUser = null;
        try {
            onlineUser = new OnlineUser(jwtUser.getUsername(), jwtUser.getAlias(), browser, ip, address, EncryptUtils.desEncrypt(token), new Date());
        } catch (Exception e) {
            e.printStackTrace();
        }
        redisUtils.set(properties.getOnlineKey() + token, onlineUser, properties.getTokenValidityInSeconds() / 1000);
    }

    /**
     * 查询全部数据
     * @param filter /
     * @param pageable /
     * @return /
     */
    public Map<String, Object> getAll(String filter, int type, Pageable pageable) {
        List<OnlineUser> onlineUsers = getAll(filter, type);
        return PageUtil.toPage(
                PageUtil.toPage(pageable.getPageNumber(), pageable.getPageSize(), onlineUsers),
                onlineUsers.size()
        );
    }

    /**
     * 查询全部数据，不分页
     * @param filter /
     * @return /
     */
    public List<OnlineUser> getAll(String filter, int type) {
        List<String> keys = null;
        if (type == 1) {
            keys = redisUtils.scan("m-online-token*");
        } else {
            keys = redisUtils.scan(properties.getOnlineKey() + "*");
        }


        Collections.reverse(keys);
        List<OnlineUser> onlineUsers = new ArrayList<>();
        for (String key : keys) {
            //OnlineUser onlineUser = (OnlineUser) redisUtils.get(key);
            OnlineUser onlineUser = BeanUtil.toBean(redisUtils.get(key), OnlineUser.class);
            if (StringUtils.isNotBlank(filter)) {
                if (onlineUser.toString().contains(filter)) {
                    onlineUsers.add(onlineUser);
                }
            } else {
                onlineUsers.add(onlineUser);
            }
        }
        onlineUsers.sort((o1, o2) -> o2.getLogintime().compareTo(o1.getLogintime()));
        return onlineUsers;
    }

    /**
     * 踢出用户
     * @param key /
     * @throws Exception /
     */
    public void kickOut(String key) throws Exception {
        key = properties.getOnlineKey() + EncryptUtils.desDecrypt(key);
        redisUtils.del(key);

    }

    /**
     * 踢出移动端用户
     * @param key /
     * @throws Exception /
     */
    public void kickOutT(String key) throws Exception {

        String keyt = "m-online-token" + EncryptUtils.desDecrypt(key);
        redisUtils.del(keyt);

    }

    /**
     * 退出登录
     * @param token /
     */
    public void logout(String token) {
        String key = properties.getOnlineKey() + token;
        redisUtils.del(key);
    }

    /**
     * 导出
     * @param all /
     * @param response /
     * @throws IOException /
     */
    public void download(List<OnlineUser> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (OnlineUser user : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("用户名", user.getUsername());
            map.put("用户昵称", user.getNickname());
            map.put("登录IP", user.getIp());
            map.put("登录地点", user.getAddress());
            map.put("浏览器", user.getBrowser());
            map.put("登录日期", user.getLogintime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    /**
     * 查询用户
     * springboot热部署  ClassCastException异常 反射 redis
     * 2-原因分析
     * 简单来说：就是类加载机制出了问题
     * 具体分析如下(参考：https://www.jianshu.com/p/e6d5a3969343)
     * 1. JVM判断两个类对象是否相同的依据:一是类全称;一个是类加载器.（具体原理请自行百度，在此不再赘述）。
     * 2. 大家都知道虚拟机的默认类加载机制是通过双亲委派实现的。springboot为了实现程序动态性
     * （比如：代码热替换、模块热部署等，白话讲就是类文件修改后容器不重启），“破坏或牺牲” 了双亲委派模型。
     * springboot通过强行干预-- “截获”了用户自定义类的加载（由jvm的加载器AppClassLoader变为
     * springboot自定义的加载器RestartClassLoader，一旦发现类路径下有文件的修改，
     * springboot中的spring-boot-devtools模块会立马丢弃原来的类文件及类加载器，
     * 重新生成新的类加载器来加载新的类文件，从而实现热部署。比较流行的OSGI也能实现热部署）。
     * @param key /
     * @return /
     */
    public OnlineUser getOne(String key) {
    	return BeanUtil.toBean(redisUtils.get(key), OnlineUser.class);
        //return (OnlineUser) redisUtils.get(key);  有异常ClassCastException异常
    }

    /**
     * 检测用户是否在之前已经登录，已经登录踢下线
     * @param username 用户名
     */
    public void checkLoginOnUser(String username, String igoreToken) {
        List<OnlineUser> onlineUsers = getAll(username, 0);
        if (onlineUsers == null || onlineUsers.isEmpty()) {
            return;
        }
        for (OnlineUser onlineUser : onlineUsers) {
            if (onlineUser.getUsername().equals(username)) {
                try {
                    String token = EncryptUtils.desDecrypt(onlineUser.getKey());
                    if (StringUtils.isNotBlank(igoreToken) && !igoreToken.equals(token)) {
                        this.kickOut(onlineUser.getKey());
                    } else if (StringUtils.isBlank(igoreToken)) {
                        this.kickOut(onlineUser.getKey());
                    }
                } catch (Exception e) {
                    log.error("checkUser is error", e);
                }
            }
        }
    }

}
